# @Author: yin.xunjie

import time
import xml.etree.ElementTree as ET
import openpyxl
import zipfile
from pathlib import Path
import sys
import getopt
import re
import csv


class E2X:
    """将 MindMaster 的 .emmx思维导图文件转换为 Excel 的 xmls文件 或 CSV文件"""

    def __init__(self, filepath, if_csv=False, case_type='', phase=''):
        self.filePath = filepath  # emmx文件路径
        self.if_csv = if_csv
        self.BasePath = Path(filepath).parent
        self.filename = Path(filepath).stem
        self.root = self.getEmmxRoot()

        # “版本”列只能放在禅道列的后面，不然会影响禅道的导入
        self.header = ['平台/分支', '所属模块', '用例标题', '前置条件', '步骤', '预期', '关键词', '优先级', '用例类型', '适用阶段',
                  '需求', '版本']
        self.sheetContent = [''] * len(self.header)  # 记录Excel每行内容
        self.sheetContent[self.header.index('用例类型')] = case_type  # 用例类型
        self.sheetContent[self.header.index('适用阶段')] = phase  # 适用阶段
        timestring = time.strftime('%Y%m%d%H%M%S', time.localtime(time.time()))

        if self.if_csv:
            self.save_path = self.BasePath.joinpath(str(self.filename) + timestring + '.csv')
            self.csv_f = open(self.save_path, 'w', encoding='UTF8', newline='')
            self.writer = csv.writer(self.csv_f)
            self.writer.writerow(self.header)
        else:
            # 初始化xmls文件
            self.save_path = self.BasePath.joinpath(str(self.filename) + timestring + '.xlsx')
            self.wb = openpyxl.Workbook()
            self.sheet = self.wb.active
            self.sheet.title = "测试用例"
            self.sheet.append(self.header)

        # 代表一条用例已经完全解析完成，需要将titleList不需要的值删除
        self.flag = False
        # 代表title处在第几层
        self.titledeep = 0

    def getEmmxRoot(self):
        """解压emmx文件并获取page.xml的root节点"""
        with zipfile.ZipFile(self.filePath) as f:
            content = f.read("page/page.xml")
        data = content.decode('utf-8')
        root = ET.fromstring(data)
        return root

    def getMainIdeaId(self):
        """获取MainIdea的ID"""
        MainIdea = self.root.find("./Shape[@Type='MainIdea']")
        ID = MainIdea.attrib.get('ID')
        # 记录MainIdea的内容
        MainIdeaText, TextList = self.getText(MainIdea)
        self.sheetContent[self.header.index('需求')] = MainIdeaText

        return ID

    def getText(self, ele):
        """
        获取该节点的文字内容
        :param ele: 节点对象
        :return: text: 文字内容字符串型式；textList：文字内容列表型式
        """
        textList = [tp.text for tp in ele.findall(".//tp")]
        text = '\n'.join(textList)
        return text, textList

    def getWholeCase(self, ID, level=0, titleList=[], deep=0):
        """
        解析xml文件，每解析一个用例就写入excel、csv新的一行
        :param ID: 节点的ID
        :param level: 模块列需要的层数
        :param titleList: 用例标题列表
        :param deep: 当前解析的节点处在xml的层级
        :return: None
        """
        self.flag = False
        element01 = self.root.find(f"./Shape[@ID='{ID}']")

        text, textList = self.getText(element01)
        if not re.findall("(平台/分支|用例标题|前置条件|步骤|预期|检查点|关键词|优先级|用例类型|适用阶段|版本)[:：]",
                          textList[0]) and element01.attrib.get(
            'Type') != "MainIdea":
            titleList.append(text)
            self.titledeep = deep
        elif textList[0].find('平台/分支') > -1:
            self.sheetContent[self.header.index('平台/分支')] = '\n'.join(textList[1:])
        elif textList[0].find('用例标题') > -1:
            self.sheetContent[self.header.index('用例标题')] = '\n'.join(textList[1:])
        elif textList[0].find('前置条件') > -1:
            self.sheetContent[self.header.index('前置条件')] = '\n'.join(textList[1:])
        elif textList[0].find('步骤') > -1:
            self.sheetContent[self.header.index('步骤')] = '\n'.join(textList[1:])
        elif textList[0].find('检查点') > -1 or textList[0].find('预期') > -1:
            self.sheetContent[self.header.index('预期')] = '\n'.join(textList[1:])
        elif textList[0].find('优先级') > -1:
            self.sheetContent[self.header.index('优先级')] = '\n'.join(textList[1:])
        elif textList[0].find('版本') > -1:
            self.sheetContent[self.header.index('版本')] = '\n'.join(textList[1:])

        # 获取element01的子节点信息
        element02 = element01.find('.//SubLevel')

        # 表示本条用例存在子节点
        if element02 is not None:
            newIDList = element02.attrib.get('V').split(';')
            for newID in newIDList:
                # 当所处理的节点的层级deep已经小于用例标题层级titledeep，删除titleList多余的内容
                if self.titledeep > deep and self.flag:
                    for i in range(self.titledeep - deep):
                        titleList.pop()
                    self.titledeep = deep
                self.getWholeCase(newID, level=level, titleList=titleList, deep=deep + 1)
        # 表示本条用例不存在子节点（本条用例已完全解析）
        else:
            self.flag = True
            module = []
            # 获取模块
            if level > 0:
                module = titleList[:level]
                titleList = titleList[level:]
            # 模块赋值
            self.sheetContent[self.header.index('所属模块')] = '_'.join(module)
            # 标题赋值
            self.sheetContent[self.header.index('用例标题')] = '_'.join(titleList)
            if self.if_csv:
                self.writer.writerow(self.sheetContent)
            else:
                self.sheet.append(self.sheetContent)
            i = self.header.index('用例标题')
            j = self.header.index('用例类型')
            self.sheetContent[i:j] = [""] * (j-i)  # 清空“用例标题”至“用例类型”的内容（不含用例类型）

    def save(self):
        """保存excel或csv文件"""
        # timestring = time.strftime('%Y%m%d%H%M%S', time.localtime(time.time()))
        # save_path = self.BasePath.joinpath(str(self.filename) + timestring + '.xlsx')
        if self.if_csv:
            self.csv_f.close()
        else:
            self.wb.save(self.save_path)
        print(f"用例写入: {self.save_path}")


using_doc = """
    e2x可以将MindMaster的emmx思维导图用例转化为Excel或CSV用例。

    使用方法:
     ①在命令行输入 e2x [-v + 数字] [-t] [-p] [--csv] [--version] [path_to_emmx_file] 
       备注：
        “-v”决定Excel中的“所属模块”的内容，默认为0（模块内容为空）
        “-t”指定用例类型的内容
        “-p”指定适用阶段的内容
        “--csv”会最终转化为csv文件（可直接导入禅道），不传则默认转化为Excel
        “--version”查询当前版本
     ②emmx文件中的对应节点第一行的内容等于Excel、csv列名，则会解析至对应的列中：
       例如：emmx某节点第一行为“前置条件”，则该内容会写入Excel、csv的“前置条件”列中。
     ③目前Excel指定了列名：平台/分支，所属模块，用例标题，前置条件，步骤，预期，关键词，优先级，用例类型，适用阶段，需求，版本。
     
    例子:
     e2x /path/to/testcase.emmx             => output /path/to/testcaseYYYYmmddHHMMSS.xmlx
     e2x -v 0 /path/to/testcase.emmx        => output /path/to/testcaseYYYYmmddHHMMSS.xmlx
     e2x -v 1 /path/to/testcase.emmx        => output /path/to/testcaseYYYYmmddHHMMSS.xmlx
     e2x -v 2 /path/to/testcase.emmx        => output /path/to/testcaseYYYYmmddHHMMSS.xmlx
     e2x -v 1 --csv /path/to/testcase.emmx  => output /path/to/testcaseYYYYmmddHHMMSS.csv
    """


def get_argv(argv):
    v_value = 0
    case_type = ''
    phase = ''
    _filepath = ''
    if_csv = False
    try:
        opts, args = getopt.getopt(argv, "v:t:p:", ['csv', 'version'])
    except getopt.GetoptError:
        print('参数错误: 请检查输入参数是否正确，查询工具简介请输入“e2x”')
        sys.exit(2)
    # 处理 返回值options是以元组为元素的列表。
    for opt, arg in opts:
        if opt == "-v":
            v_value = int(arg)
        elif opt == "-t":
            case_type = arg
        elif opt == "-p":
            phase = arg
        elif opt == '--csv':
            if_csv = True
        elif opt == '--version':
            print('V2.1_20231115')
            sys.exit(2)
    try:
        _filepath = args[0]
    except IndexError as e:
        print(using_doc)
        sys.exit()

    return v_value, _filepath, if_csv, case_type, phase


if __name__ == '__main__':
    level, filepath, if_csv, case_type, phase = get_argv(sys.argv[1:])
    E = E2X(filepath, if_csv, case_type, phase)
    ID = E.getMainIdeaId()
    E.getWholeCase(ID, level=level)
    E.save()
